package main

import (
	"fmt"
)

func main() {
	//首先 slice底层包含三个元素，分别是指向底层数组的指针、当前切片长度(切片可访问底层数组的范围)、切片的容量（底层数组的大小）
	//其次 go只有值传递 这两点要牢记

	//一.基本
	fmt.Println("一.基本")
	//切片s1的长度是3，容量是5
	s1 := make([]int, 3, 5)
	println(s1) //[3/5]0xc000039f50  含义：[长度/容量]目前指向的底层数组地址
	fmt.Println("长度", len(s1), "容量", cap(s1))
	//一定要注意 目前该切片持有的底层数组长度是5 这个会影响后面的扩容机制
	fmt.Println(s1)            //[0 0 0] 由于长度为3，因此只能范围到底层数组的前三个元素
	s2 := make([]int, 5)       //s2的长度和容量都是5
	println(s2)                //[5/5]0xc000107ef0
	s3 := []int{1, 2, 3, 4, 5} //s3的长度和容量都是5
	println(s3)                //[5/5]0xc000107ec8
	//由于切片底层是数组，所以可以通过索引访问元素
	fmt.Println(s3[2]) //输出3
	//同样可以进行修改
	s3[2] = 30
	fmt.Println(s3) //输出[1,2,30,4,5]
	//s1长度为3，因此访问不了超过其长度的元素
	//fmt.Println(s1[3])//panic: runtime error: index out of range [3] with length 3

	//二.关于nil切片和空切片的区别
	fmt.Println("二.关于nil切片和空切片的区别")
	var nilSlice []int
	var emptySlice = make([]int, 0)
	fmt.Println(nilSlice)   //输出[]
	fmt.Println(emptySlice) //输出[]
	//虽然都输出[],其长度和容量都为0
	//但是本质不同，nil切片其底层数组是未分配内存的，而空切片底层数组是存在的，仅仅是一个长度为0不包含任何元素的数组
	println(nilSlice)   //[0/0]0x0  可以看到 地址是0x0
	println(emptySlice) //[0/0]0x5a4d88

	//无论是nil还是空切片，都可以进行操作，对nil切片的操作是安全的 ,go内部函数已经做了处理 这点和map不同
	//迭代nil切片 不会panic
	for i, v := range nilSlice {
		fmt.Println(i, v)
	}
	fmt.Println(len(nilSlice), cap(nilSlice)) // 0 0
	//append追加
	nilSlice = append(nilSlice, 1)
	println(nilSlice)

	//三.切片拷贝
	//拷贝后两个切片就没关系了
	c1 := []int{1, 2, 3}
	c2 := make([]int, 2, 5)
	copyLen := copy(c2, c1)
	fmt.Println("拷贝的长度:", copyLen) //始终依赖目标切片的长度，因此拷贝2个元素
	fmt.Println(c2)                //[1 2]
	println(c2)                    //[2,5]
	c2[0] = 100
	fmt.Println(c1) //依然是[1 2 3] 因为两者不共享同一个底层数组

	//四.进行切片
	fmt.Println("四.进行切片")
	//语法 [A:B:C] A:开始索引 B：结束索引（不包含） C：容量大小
	//新切片的长度为 B-A 容量为C-A
	//比如：
	s4 := []int32{10, 20, 30, 40, 50}
	println(s4) //[5/5]0xc00000c400
	t1 := s4[1:3:4]
	println(t1)     //[2/3]0xc00000c404  可以看到 t1的数组地址是s4的地址加上4个字节，正好是int32的大小
	fmt.Println(t1) //[20 30]
	//目前t1和s4共享底层数组，对t1的修改也会影响s4
	t1[0] = 200
	fmt.Println(s4) //[10 200 30 40 50]
	//[A:B:C]并不非要指定全，可以省略
	fmt.Println(s4[:], len(s4[:]), cap(s4[:]))    //代表将s4整体做切片 长度和容量均为5
	fmt.Println(s4[1:], len(s4[1:]), cap(s4[1:])) //代表从索引为1开始一直切到末尾 长度是4 容量是4
	fmt.Println(s4[:3], len(s4[:3]), cap(s4[:3])) //代表从头切到索引为2 长度是3 容量是5

	//五.append和扩容机制
	fmt.Println("五.append和扩容机制")
	s5 := []int{11, 22, 33, 44, 55}
	s6 := s5[1:3:4]
	fmt.Println(s6) //[22 33]
	println(s6)     //[2/3]0xc000122158
	//s6的长度是2，容量是3,对其追加一个元素
	s6 = append(s6, 66)
	fmt.Println(s6) //[22 33 66]
	println(s6)     //[3/3]0xc000122158 注意此时底层数组地址没有变化
	//**也就是说追加进去的66也影响s5了
	fmt.Println(s5) //[11 22 33 66 55]
	//以上是因为s6并没有满足扩容的情况，下面对其再次追加，因为s6的容量只有3，再次追加就需要扩容
	//一旦扩容，s6和s5不再共享同一个底层数组
	s6 = append(s6, 77)
	fmt.Println(s6) //[22 33 66 77]
	println(s6)     //[4/6]0xc0001221b0   注意容量扩容了，变成了原先的两倍 底层数组地址也变了
	fmt.Println(s5) //s5并没有收到影响，依然是[11 22 33 66 55] 因为已经不是同一个底层数组
	//只要切片的长度等于容量，追加元素就会扩容
	s7 := []int{11, 22, 33, 44, 55}
	s8 := s7[1:3:4]
	fmt.Println(s7)
	s8 = append(s8, 66, 77)
	//s8的长度是2，容量是3，直接追加两个元素，需要进行扩容
	//注意扩容不是发生在追加了66之后，而是一开始，所以完全不会影响s7
	fmt.Println(s8) //[22 33 66 77]
	fmt.Println(s7) //[11 22 33 44 55]
	//**关于扩容机制
	const N = 1024
	var a [N]int //长度为1024的数组
	//a[:N-1:N] 一个长度为 1023-0=1023 容量为1024的切片
	//追加两个元素 发生扩容，go1.16之前是按照长度标准进行扩容的
	//当扩容前长度小于1024时，容量直接变为原容量的2倍
	//目前长度是1023，容量是1024，因此直接扩容到2048
	n1 := append(a[:N-1:N], 10, 10)
	//a[:N:N] 一个长度为 1024-0=1024 容量为1024的切片
	//追加一个元素 发生扩容，而扩容前长度为1024，因此不是按照原容量翻倍 而是提升25% 1024*0.25+1024=1280
	n2 := append(a[:N:N], 10)
	println(cap(n1), cap(n2))

	//但是go1.16开始 是按照原容量的大小进行扩容的，因此从1.16开始
	//n1和n2扩容后的容量都是1280

	//六.作为实参传递
	s9 := make([]int, 3, 5)
	s9[0] = 1
	s9[1] = 2
	s9[2] = 3
	changeSlice(s9)
	//输出[10 2 3] 之所以会这样
	//因为changeSlice内部仅追加了2个元素，并不会发生扩容，因此其内部的s和外部的s9共享底层数组
	//修改内部s第一个元素为10后，s9也被修改
	//注意go是值传递，内部s的长度被修改到了5 不影响s9
	fmt.Println(s9)

	changeSlice2(s9)
	//依然输出[10 2 3]
	//因此内部s扩容超过了原容量，和外部s9不是同一个底层数组
	fmt.Println(s9)

	//七.一些技巧
	fmt.Println("七.一些技巧")
	fmt.Println("在切片指定位置插入元素")
	nums := []interface{}{1, 2, 3, 4, 5}
	nums = InsertSlice(nums, 2, 10)
	fmt.Println(nums)

}

func changeSlice(s []int) {
	s = append(s, 4, 5)
	s[0] = 10
}

func changeSlice2(s []int) {
	s = append(s, 4, 5, 6)
	s[0] = 100
}

func InsertSlice(slice []interface{}, index int, ele interface{}) []interface{} {
	if index < 0 {
		panic("index error!")
	}
	if index == 0 {
		//在开头插入，其实就是将原切片追加到需要插入的元素后面作为新切片
		return append([]interface{}{ele}, slice...)
	}
	//在非开头插入 就是将 原切片中插入位置之后的内容追加到插入元素后 再将其整体追加到原切片插入位置之前的元素后
	return append(slice[:index], append([]interface{}{ele}, slice[index:]...)...)
}
